using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics.CodeAnalysis;
using System.IO;
using System.Runtime.Serialization;
using System.Windows;
using System.Windows.Documents;
using System.Windows.Markup;
using System.Xml.Serialization;
using Bookvar.ObjectModel.Actions.Collaboration;
using Bookvar.ObjectModel.Collections;

namespace Bookvar.ObjectModel
{
    /// <summary>
    /// A topic in the mind map. The topic is the main entity in a mind map.
    /// </summary>
    [ContentProperty( "SubTopics" )]
    [DataContract]
    [KnownType( typeof( MainTopic ) )]
    [Serializable]
    public class Topic : INotifyPropertyChanged
    {
        // If during desirialization breaks - remove readonly
        [DataMember( Name = "ID" )]
        private readonly Guid id;
        private string name;
        private AnnotationCollection annotations;
        private AttachmentCollection attachments;
        private ResourceCollection resources;
        private CustomPropertyCollection customProperties;
        [DataMember( Name = "Hyperlink" )]
        private string hyperlink;
        [NonSerialized]
        private TextRange note;
        [DataMember]
        private byte[] noteBytes;
        private Topic parent;
        private TopicCollection subTopics;

        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "subTopics")]
        [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors")]
        public Topic( Guid id, string name, IEnumerable<Annotation> annotations, IEnumerable<Attachment> attachments,
                      IEnumerable<Resource> resources, IEnumerable<CustomProperty> customProperties,
                      IEnumerable<Topic> subTopics, TextRange note, Topic parent )
        {
            this.id = id;
            this.name = name;
            this.annotations = new AnnotationCollection(annotations);
            this.attachments = new AttachmentCollection( attachments );
            this.customProperties = new CustomPropertyCollection( customProperties );
            this.resources = new ResourceCollection( resources );
            this.subTopics = new TopicCollection( this, subTopics );
            this.Note = note;
            this.parent = parent;
            this.subTopics.CollectionChanged += OnSubTopicsChanged;

        }

        public Topic( )
            : this( Guid.NewGuid(), String.Empty, new List<Annotation>(), new AttachmentCollection(),
                new ResourceCollection(), new CustomPropertyCollection(), new List<Topic>(), null, null )
        {
        }

        public Topic( Guid id, string name, TextRange note, Topic parent ) :
            this( id, name, new List<Annotation>(), new AttachmentCollection(), new ResourceCollection(),
             new CustomPropertyCollection(), new List<Topic>(), note, parent )
        { }

        public Topic( Topic topic )
            : this( topic.ID, topic.Name, topic.Annotations, topic.Attachments, topic.Resources,
                   topic.CustomProperties, topic.SubTopics, topic.Note, topic.Parent )
        { }

        #region Properties

        //[DataMember]
        [XmlIgnore]
        public Topic Parent
        {
            get { return parent; }
            set
            {
                if ( value != parent )
                {
                    if ( parent != null )
                        DoActionEvent -= parent.SubTopicDoActionEvent;

                    parent = value;

                    if ( parent != null )
                        DoActionEvent += parent.SubTopicDoActionEvent;

                    OnPropertyChanged( new PropertyChangedEventArgs( "Parent" ) );
                }
            }
        }

        [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "ID")]
        public Guid ID
        {
            get { return id; }
        }

        //[DataMember]
        [XmlIgnore]
        // TODO: Why note is TextRange?!? It should be simple text.
        public TextRange Note
        {
            get
            {
                if ( note == null && noteBytes != null )
                {
                    note = DeserialiseNotes( noteBytes );
                }

                return note;
            }
            set
            {
                if ( value != note )
                {
                    note = value;
                    noteBytes = SerialiseNotes( note );
                    OnPropertyChanged( new PropertyChangedEventArgs( "Note" ) );
                }
            }
        }

        private static TextRange DeserialiseNotes( byte[] noteBytes )
        {
            if ( noteBytes != null )
            {
                FlowDocument doc = new FlowDocument();
                TextRange range = new TextRange( doc.ContentStart, doc.ContentEnd );
                using ( MemoryStream stream = new MemoryStream( noteBytes ) )
                {
                    range.Load( stream, DataFormats.XamlPackage );
                    return range;
                }
            }
            return null;
        }

        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Used in serialization.")]
        [DataMember]
        public ResourceCollection Resources
        {
            get { return resources; }
            set { resources = value; }
        }

        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubTopics"), SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Used in serialization.")]
        [DataMember]
        public TopicCollection SubTopics
        {
            get { return subTopics; }
            set { subTopics = value; }
        }

        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Used in serialization.")]
        [DataMember]
        public AttachmentCollection Attachments
        {
            get { return attachments; }
            set { attachments = value; }
        }

        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Used in serialization.")]
        [DataMember]
        public AnnotationCollection Annotations
        {
            get { return annotations; }
            set { annotations = value; }
        }

        [SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification = "Used in serialization.")]
        [DataMember]
        public CustomPropertyCollection CustomProperties
        {
            get { return customProperties; }
            set { customProperties = value; }
        }

        [DataMember]
        public string Name
        {
            get { return name; }
            set
            {
                if ( value != name )
                {
                    name = value;
                    OnPropertyChanged( new PropertyChangedEventArgs( "Name" ) );

                    ChangeTopicNameAction action = new ChangeTopicNameAction( id, name );
                    ActionEventArgs args = new ActionEventArgs( action );
                    OnDoActionEvent( args );
                }
            }
        }

        [XmlIgnore]
        public virtual bool IsMainTopic
        {
            get { return false; }
        }

        public Uri Hyperlink
        {
            get
            {
                if ( !string.IsNullOrEmpty( hyperlink ) )
                {
                    return new Uri( hyperlink, UriKind.RelativeOrAbsolute );
                }
                return null;
            }
            set
            {
                if ( value == null && string.IsNullOrEmpty( hyperlink ) )
                {
                    return;
                }

                if ( value != null && value.ToString() == hyperlink )
                {
                    return;
                }

                if ( value != null )
                {
                    hyperlink = value.ToString();
                }
                else
                {
                    hyperlink = null;
                }

                OnPropertyChanged( new PropertyChangedEventArgs( "Hyperlink" ) );
            }
        }

        public int Depth
        {
            get
            {
                return GetDepth( this );
            }
        }

        public bool IsFloating
        {
            get
            {
                return Parent == null && IsMainTopic == false;
            }
        }

        #endregion

        #region DoActionEvent Members

        [field: NonSerialized]
        public event EventHandler<ActionEventArgs> DoActionEvent;

        protected virtual void OnDoActionEvent( ActionEventArgs args )
        {
            if ( DoActionEvent != null )
            {
                DoActionEvent( this, args );
            }
        }

        #endregion

        #region INotifyPropertyChanged Members
        //    [Field:XmlIgnore]
        [field: NonSerialized]
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged( PropertyChangedEventArgs args )
        {
            if ( PropertyChanged != null )
            {
                PropertyChanged( this, args );
            }
        }

        #endregion

        public override int GetHashCode( )
        {
            return id.GetHashCode();
        }

        public override string ToString( )
        {
            return name;
        }

        [SuppressMessage("Microsoft.Security", "CA2109:ReviewVisibleEventHandlers")] 
        [SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "SubTopics")]
        protected virtual void OnSubTopicsChanged( object sender, NotifyCollectionChangedEventArgs e )
        {
            switch ( e.Action )
            {
                case NotifyCollectionChangedAction.Add:
                    foreach ( Topic topic in e.NewItems )
                    {
                        ActionEventArgs args = new ActionEventArgs( new AddTopicAction( ID, topic, e.NewStartingIndex ) );
                        OnDoActionEvent( args );
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    break;
                case NotifyCollectionChangedAction.Remove:
                    foreach ( Topic topic in e.OldItems )
                    {
                        ActionEventArgs args = new ActionEventArgs( new RemoveTopicAction( topic.ID ) );
                        OnDoActionEvent( args );
                    }
                    break;
                case NotifyCollectionChangedAction.Replace:
                    break;
                case NotifyCollectionChangedAction.Reset:
                    break;
                default:
                    break;
            }
        }

        private static int GetDepth( Topic topic )
        {
            if ( topic.parent == null )
            {
                return 0;
            }
            
            return 1 + GetDepth( topic.Parent );
        }

        private static byte[] SerialiseNotes( TextRange note )
        {
            if ( note != null )
            {
                using ( MemoryStream stream = new MemoryStream() )
                {
                    note.Save( stream, DataFormats.XamlPackage );
                    byte[] noteBytes = new byte[ stream.Length ];
                    stream.Seek( 0, SeekOrigin.Begin );
                    stream.Read( noteBytes, 0, (int) stream.Length );
                    return noteBytes;
                }
            }
            return null;
        }

        public Topic FindTopic( Guid topicId )
        {
            if ( id == topicId )
            {
                return this;
            }
            
            if ( SubTopics.Count > 0 )
            {
                foreach ( Topic topic in SubTopics )
                {
                    Topic found = topic.FindTopic( topicId );
                    if ( found != null )
                    {
                        return found;
                    }
                }
            }

            return null;
        }

        public Topic Clone( )
        {
            Topic clone = new Topic( Guid.NewGuid(), name, note, parent );
            clone.attachments = attachments;
            clone.resources = resources;
            clone.annotations = annotations;
            foreach ( Topic topic in subTopics )
            {
                clone.subTopics.Add( topic.Clone() );
            }

            return clone;
        }

        private void SubTopicDoActionEvent( object sender, ActionEventArgs args )
        {
            OnDoActionEvent( args );
        }

        [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "context")]
        [OnDeserialized]
        private void SetParentAfterDeserialized( StreamingContext context )
        {
            subTopics.Parent = this;
            foreach ( Topic subTopic in SubTopics )
            {
                subTopic.Parent = this;
            }

            SubTopics.CollectionChanged += OnSubTopicsChanged;
        }

        //[OnSerializing()]
        //private void SerializeNotes(StreamingContext context)
        //{
        //    context.Context = 
        //    subTopics.Parent = this;
        //    foreach (Topic subTopic in SubTopics)
        //    {
        //        subTopic.Parent = this;
        //    }

        //    SubTopics.CollectionChanged += OnSubTopicsChanged;
        //}
    }
}
